/*
	Virtual Memory
*/
#include <stdint.h>
#include "kernel.h"
#include "vm.h"
#include "pm.h"

unsigned char ____vm_init = 0;

int vm_init()
{
	____vm_init = 1;
	return 1;
}

uint32_t *____vm_rlook = 0;

void vm_rlook_set(uint32_t *kvmd, uint32_t p, uint32_t v)
{
	uint32_t x;
	p &= ~0xFFF;
	v &= ~0xFFF;
	if(!____vm_rlook)
	{
		____vm_rlook = (uint32_t*)pm_alloc();
		for(x = 0; x < 1024; ++x)
		{
			____vm_rlook[x] = 0;
		}
		____vm_rlook[p >> 22] = pm_alloc();
		for(x = 0; x < 1024; ++x)
		{
			((uint32_t*)____vm_rlook[p >> 22])[x] = 0;
		}
	}else{
		if(!____vm_rlook[p >> 22])
		{
			____vm_rlook[p >> 22] = vm_alloc(kvmd, kvmd, VM_KERNEL);
			for(x = 0; x < 1024; ++x)
			{
				((uint32_t*)____vm_rlook[p >> 22])[x] = 0;
			}
		}
	}
	((uint32_t*)____vm_rlook[p >> 22])[p << 10 >> 22] = v;
	return;
}
uint32_t vm_rlook_what(uint32_t p)
{
	p &= ~0xFFF;
	if(!____vm_rlook)
	{
		kdbgerror("failed at translation of %x; locking system.\n", p);
		asm("cli; hlt; nop; nop;");
		return 0xFFF;
	}
	if(!____vm_rlook[p >> 22])
	{
		kdbgerror("failed at translation of %x; locking system.\n", p);
		asm("cli; hlt; nop; nop;");
		return 0xFFF;
	}
	return ((uint32_t*)____vm_rlook[p >> 22])[p << 10 >> 22];
}

uint32_t vm_look_what(uint32_t *vmd, uint32_t v)
{
	uint32_t *t;
	vmd = (uint32_t*)vm_rlook_what((uint32_t)vmd);
	if(!vmd[v >> 22])
	{
		kdbgerror("failed at translation of %x; locking system.\n", v);
		asm("cli; hlt; nop; nop;");
		return 0xFFF;
	}
	t = (uint32_t*)vm_rlook_what(vmd[v >> 22] & ~0xFFF);
	if(!t[v << 10 >> 22])
	{
		kdbgerror("failed at translation of %x; locking system.\n", v);
		asm("cli; hlt; nop; nop;");
		return 0xFFF;
	}
	return t[v << 10 >> 22] & ~0xFFF;
}

uint32_t vm_pm_talloc(uint32_t *kvmd)
{
	
	if(!____vm_init)
	{
		return pm_alloc();
	}
	return vm_alloc(kvmd, kvmd, VM_KERNEL);
}

uint32_t vm_talloc(uint32_t *kvmd, uint32_t *vmd, uint32_t index, uint32_t flags)
{
	uint32_t p, v;
	if(!____vm_init)
	{
		p = pm_alloc();
		v = p;
	}else{
		p = pm_alloc();
		v = vm_find(kvmd, kvmd, 0, 0xffffffff >> 22, 1);
	}
	vmd[index] = p | flags;
	vm_rlook_set(kvmd, p, v);
	vm_map(kvmd, kvmd, p, v, VM_KERNEL);
	return v;
}

uint32_t* vm_make(uint32_t *kvmd)
{
	uint32_t *nvmd, x;
	if(!kvmd)
	{
		nvmd = (uint32_t*)pm_alloc();
		vm_rlook_set(nvmd, (uint32_t)nvmd, (uint32_t)nvmd);
		vm_map(nvmd, nvmd, (uint32_t)nvmd, (uint32_t)nvmd, VM_KERNEL);
	}else{
		nvmd = (uint32_t*)vm_alloc(kvmd, kvmd, VM_KERNEL);
		vm_rlook_set(kvmd, vm_look_what(kvmd, (uint32_t)nvmd), (uint32_t)nvmd);
	}
	for(x = 0; x < 1024; ++x)
	{
		nvmd[x] = 0;
	}
	return nvmd;
}

void vm_map(uint32_t *kvmd, uint32_t *vmd, uint32_t p, uint32_t v, uint32_t flags)
{
	uint32_t x, *t;
	p &= ~0xfff;
	v &= ~0xfff;
	flags &= 0xfff;

	vmd = (uint32_t*)vm_rlook_what((uint32_t)vmd);
	if(!vmd[v >> 22])
	{
		/// No, table has been allocated. Allocate, one.
		t = (uint32_t*)vm_talloc(kvmd, vmd, v >> 22, flags);
		for(x = 0; x < 1024; ++x)
		{
			t[x] = 0;
		}
	}else{
		/// translate physical address to virtual mapped one.
		t = (uint32_t*)vm_rlook_what(vmd[v >> 22] & ~0xFFF);
	}
	t[v << 10 >> 10 >> 12] = p | flags;
	return;
}

void vm_map2(uint32_t *kvmd, uint32_t *vmd, uint32_t p, uint32_t v, uint32_t flags, uint32_t count)
{
	uint32_t x;
	for(x = 0; x < count; ++x)
	{
		vm_map(kvmd, vmd, p + (x * 0x1000), v + (x * 0x1000), flags);
	}
	return;
}

uint32_t vm_find(uint32_t *kvmd, uint32_t *vmd, uint32_t start, uint32_t end, uint32_t count)
{
	uint32_t x, y, origin, found, *t;
	origin = 0;
	start = start >> 22;
	end = end >> 22;
	found = 0;

	vmd = (uint32_t*)vm_rlook_what((uint32_t)vmd);
	for(x = start; x <= end; ++x)
	{
		if(!vmd[x])
		{
			found += 1024;
			if(found >= count)
			{
				return origin;
			}
		}else{
			t = (uint32_t*)vm_rlook_what(vmd[x] & ~0xFFF);
			for(y = 0; y < 1024; ++y)
			{
				if(!t[y])
				{
					++found;
					if(found >= count)
					{
						return origin;
					}
				}else{
					origin = (x << 22) | ((y+1) << 12);
					found = 0;
				}
			}
		}
	}
	return 0xFFF;
}

uint32_t vm_alloc2(uint32_t *kvmd, uint32_t *vmd, uint32_t count, uint32_t flags)
{
	uint32_t origin = vm_find(kvmd, vmd, 0, 0xffffffff >> 22, count), x;
	/// map pages found for allocation.
	for(x = 0; x < count; ++x)
	{
		vm_map(kvmd, vmd, pm_alloc(), origin + (x * 0x1000), flags);
	}
	return origin;
}

uint32_t vm_alloc(uint32_t *kvmd, uint32_t *vmd, uint32_t flags)
{
	return vm_alloc2(kvmd, vmd, 1, flags);	
}

int32_t vm_copy2(uint32_t *kvmd, uint32_t *fvmd, uint32_t *dvmd, uint32_t fv, uint32_t tv, uint32_t c, uint32_t flags)
{
	uint32_t x;
	for(x = 0; x < c; ++x)
	{
		vm_map(kvmd, dvmd, vm_look_what(fvmd, fv + (x * 0x1000)), tv + (x * 0x1000), flags);
	}
	return 1;
}

uint32_t vm_copy3(uint32_t *kvmd, uint32_t *fvmd, uint32_t *dvmd, uint32_t fv, uint32_t c, uint32_t flags)
{
	uint32_t x;
	uint32_t tv = vm_find(kvmd, dvmd, 0, 0xffffffff >> 22, c);
	if(tv == 0xFFF)
	{
		kdbgwarn("could find enough free memory in address space.\n");
		return 0xFFF;
	}
	for(x = 0; x < c; ++x)
	{
		vm_map(kvmd, dvmd, vm_look_what(fvmd, fv + (x * 0x1000)), tv + (x * 0x1000), flags);
	}
	return tv;
}

void vm_free(uint32_t *kvmd, uint32_t *vmd, uint32_t v)
{
	uint32_t *t;
	vmd = (uint32_t*)vm_rlook_what((uint32_t)vmd);
	if(!vmd[v >> 22])
	{
		kdbgwarn("trying to free non-existant virtual memory!\n");
		return;
	}
	t = (uint32_t*)vm_rlook_what(vmd[v >> 22] & ~0xFFF);
	if(!t[v << 10 >> 22])
	{
		kdbgwarn("trying to free non-existant virtual memory!\n");
		return;
	}
	pm_free(t[v << 10 >> 22] & ~0xFFF);
	t[v << 10 >> 22] = 0;
	return;
}
void vm_free2(uint32_t *kvmd, uint32_t *vmd, uint32_t v, uint32_t c)
{
	uint32_t x;
	for(x = 0; x < c; ++x)
	{
		vm_free(kvmd, vmd, v + x * 0x1000);
	}
	return;
}


